#include<iostream>
#include<cstring>
#include<cerrno>
#include<cassert>
#include<cstdlib>
#include<vector>
#include<string>
#include<unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<fcntl.h>
#include <sys/wait.h>


using namespace std;

#define NUM_PROC 5 //子进程个数
#define NUM_TASK 3 //任务个数

typedef void (*func)();//函数指针类型

//记录子进程与对应管道信息
struct proc
{
    //构造
    proc(int pid,int write_fd)
    :_pid(pid)
    ,_write_fd(write_fd)
    {
        _num++;
        char buffer[1024];//暂存进程名称
        snprintf(buffer,sizeof(buffer),"process-%d[pid(%d)-fd(%d)]",_num,_pid,_write_fd);
        _name=buffer;
    }

    //析构
    ~proc()
    {
        _num--;
    }

    static int _num;//记录对象个数
    int _pid;//进程id
    int _write_fd;//管道写端描述符
    string _name;
};

int proc::_num=0;//初始化

//刷新任务
void fflushTask()
{
    cout<<" 刷新任务,pid:"<<getpid()<<endl;
}

//IO任务
void ioTask()
{
    cout<<" IO任务,pid:"<<getpid()<<endl;
}

//下载任务
void downloadTask()
{
    cout<<" 下载任务,pid:"<<getpid()<<endl;
}

//加载任务列表
void Loadtask(vector<func>& taskMap)
{
    taskMap.push_back(fflushTask);
    taskMap.push_back(ioTask);
    taskMap.push_back(downloadTask);
}


//创建管道与子进程，并维护与每个子进程的通信信道
void Loadproc(vector<proc>& procMap,vector<func>& taskMap,vector<int>& write_fds)
{
    //创建子进程
    for(int i=0;i<NUM_PROC;i++)
    {
        int fds[2];//用于接收管道描述符
        int n=pipe(fds);//创建管道
        assert(n==0);//n为0表示管道创建成功

        int pid=fork();//创建子进程
        assert(pid>=0);//pid<0则创建失败

        
        if(pid==0)//子进程,从管道中读取数据
        {
            close(fds[1]);//子进程先关闭写端
            //同时继承自父进程的不属于自己的写端描述符
            for(int i=0;i<write_fds.size();i++)
            close(write_fds[i]);

            while(true)
            {
                int command=0;//接收任务编号
                int n=read(fds[0],&command,sizeof(int));//从管道中读取任务编号
                assert(n==4 || n==0);//必须读取4个字节或0个字节
                
                if(n==4)//命令读取成功
                {
                    if(command>=0 && command<taskMap.size())//任务编号合法
                    taskMap[command]();//执行对应编号的任务
                    else//任务编号不合法
                    {
                        cout<<"任务编号错误"<<endl;
                        break;
                    }
                }
                else if(n==0)//写端已关闭，关闭读端
                {
                    cout<<"写端退出，读端也退出"<<endl;
                    break;
                }
                else//读取异常
                {
                    cout<<"读取异常："<<strerror(errno)<<endl;
                    break;
                }
            }

            close(fds[0]);//通信结束，子进程关闭读端
            exit(0);//子进程退出
        }

        //父进程
        close(fds[0]);//父进程关闭读端
        proc tmp(pid,fds[1]);
        procMap.push_back(tmp);//记录对应的子进程和管道写端文件描述符
        write_fds.push_back(fds[1]);//存储所有打开的写端描述符

    }
}


//随机选择一个任务发送给随机的子进程
void Sendcommand(vector<proc>& procMap,vector<func>& taskMap,vector<int>& write_fds,int count)
{
    //count为0表示永不停歇，count不为0则发送count次任务
    bool forever=count==0?true:false;
    while(forever || count>0)
    {
        int pi=rand()%procMap.size();//进程序号
        int command=rand()%taskMap.size();//任务编号

        cout<<"send task:"<<command<<" to "<<procMap[pi]._name<<endl;
        int n=write(procMap[pi]._write_fd,&command,sizeof(command));//将任务编号发送到对应的管道中
        assert(n>=0);//n<0则写入失败
        count--;//次数--
        sleep(2);//每隔两秒发一次
    }

    //发送完毕，关闭所有写端描述符
    for(int i=0;i<write_fds.size();i++)
    close(write_fds[i]);

}

//回收子进程
void waitprocess(vector<proc>& procMap)
{
    for(int i=0;i<procMap.size();i++)
    waitpid(procMap[i]._pid,nullptr,0);
}

int main()
{
    srand(time(nullptr));//随机数种子

    //1、加载任务
    vector<func> taskMap;//任务列表，每一个元素都是一个函数指针
    Loadtask(taskMap);//加载任务到任务列表

    //2、创建管道与子进程，并维护与每个子进程的通信信道
    vector<proc> procMap;//记录每一个子进程和对应的管道的写端描述符
    vector<int> write_fds;//存储所有管道的写端描述符
    Loadproc(procMap,taskMap,write_fds);//加载子进程

    //父进程
    //3、随机选择一个任务发送给随机的子进程
    Sendcommand(procMap,taskMap,write_fds,10);

    //4、回收子进程
    waitprocess(procMap);

    return 0;
}